<?php
    abstract class ContentCourierFactory extends ContentCourier{
        static public function getInstance($type='Default',$content_loader_type='FileSystem',$site_config_type='XML',$options){
            $type = trim($type);
            switch($type){
                default:
                    $type = ('' == trim($type) || 'default' == strToLower($type) ) ? 'Default' : $type;
                    $type = 'ContentCourier'.$type;
                    $object = new $type($options,$content_loader_type);                    
                    return $object;        
            }                
        }                
    }
    
    class ContentCourierDefault extends ContentCourierFactory{
        protected function handleOptions($options){
            $this->contentLoader->basePath = $options['base-file-path'];        
            #parent::handleOptions($options);
        }
    }
    
    class ContentCourier{
        //current page URI we're loading
        /**
        * The URI of the requested Page
        * @var string $pageURI
        */         
        protected $pageURI;

        /**
        * An array of string with our section names
        * @var array $sections
        */                  
        protected $sections;

        /**
        * THe HTML, XHTML, FlavorOfTheYearTML template
        *
        * with <section-name /> tags that corespond to $sections above
        * @var type $varName
        */                 
        protected $template;

        /**
        * Object holding our config file
        * @var Object $siteConfig 
        */                 
        protected $siteConfig;

        /**
        * Our Content Loading Object.  Content Loadsr take 
        * content URIs and return a string
        * @var Object $contentLoader
        */         
        protected $contentLoader;
        
        /**
        * Strict 404 handling 
        *
        * If a valid-uri isn't found, strict 404 handling will allow
        * the 404 exception to kill the page process.  Non-strict handling will
        * leave the behavior up to the application developer
        *
        * @var boolean $strict404Handling
        */ 
        protected $strict404Handling;
        
        /**
        * Standard Contsructor
        *
        * An Array of options.  Right now the following fields are supported
        *
        * $options['base-file-path'] 
        * base path of the content-store, used in the default delivery application
        *
        * $options['config-uri']     = 'config.xml';
        * URI of the config file 
        *
        * @param array $options
        */         
        public function __construct($options,$content_loader_type,$site_config_type='XML'){      
            $this->initialize($options,$content_loader_type);
        }    

        /**
        * Override and do anything you'd like with passed in options
        * @param array $options
        */
        protected function handleOptions($options){
            
        }
        
        /**
        * Initializes, sets everything up, int main void etc.
        * @param array $options See constructor
        */         
        protected function initialize($options,$content_loader_type,$site_config_type='XML'){ 
            $this->contentLoader = ContentCourierLoaderFactory::getInstance($content_loader_type);        
            $this->contentLoader->contentCourierApplication = $this;            
            
            
            //load up template to use based on page URI                   
            $this->setPageURI();
            $this->setConfig($options['config-uri'],$site_config_type);
                        
            $this->setStrict404Handling();
            
            try{
                $this->checkValidURI();
            }
            catch (ContentCourierHTTP404_Exception $e){
                if($this->strict404Handling){
                    $e->quitAndExit("Could not find URI [".$this->pageURI."]");
                }
            }
            $this->setTemplate();              
            $this->registerSectionsFromConfig();        
            
            $this->handleOptions($options);            
        }
        
        protected function setStrict404Handling(){
            $this->siteConfig->getStrict404Handling();
            if('false' == $sStrict){
                $this->strict404Handling = false;
            }
            else{
                $this->strict404Handling = true;            
            }
        }
        
        /**
        * Checks to see if the page URI is valid
        *
        * Checks the config file to see if the Page URI passed in is actually
        * configured for this site.  If not throws a 404 exceptions, which 
        * can be used to output a 404 page/header or handled 
        */                 
        protected function checkValidURI(){
            if(!$this->siteConfig->isValidPageURI($this->pageURI)){
                throw new ContentCourierHTTP404_Exception('URI ['+$this->pageURI+'] not found/configured');
            }
        }
        
        /**
        * Sets the config property
        *
        * Don't overload this, instead overload loadCongigUrl
        * @param string $sConfigURI
        */                 
        protected function setConfig($sConfigURI,$type='XML'){
            $this->siteConfig = $this->loadConfigURI($sConfigURI,$type);
        }

        /**
        * Does the actual loading of the config file
        *
        * Overload this all your want to add caching, load the config from a 
        * database or whatever
        * Multi Line Description
        * @param type $sConfigURI
        * @return Object ContentCourierConfigSiteXML
        */                 
        protected function loadConfigURI($sConfigURI,$type='XML'){
            $siteConfig = ContentCourierConfigSiteFactory::getInstance($type,$sConfigURI);
            if(!$siteConfig){
                throw new Exception("Could not create siteConfig");
            }
            return $siteConfig;
        }
        
        /**
        * Registers valid sections
        *
        * Creates a section object, which is used to do the actual content
        * loading and apply any special rules to a particular section
        *
        * Throws the objects in a sections property for later use
        * @param string $section
        * @param object $section_config ContentCourierSiteConfig
        */                 
        public function registerSection($section,$section_config){
            //add in section validation?
            if(!isset($this->sections)){
                $this->sections = Array();
            }
            
            if (!in_array ( $section, $this->sections) ){
                #$sDefaultContentProcessor = (string) $this->xmlConfig->sections->attributes()->defaultProcessor;
                $sDefaultContentProcessor = $this->siteConfig->getDefaultContentProcessor();
                                
                $sSectionContentProcessor = $section_config->getContentProcessor();
                $content_processor = ('' !== trim($sSectionContentProcessor)) ? $sSectionContentProcessor : $sDefaultContentProcessor;
                if('' == trim($content_processor)){
                    throw new Exception("Could not find content processor");
                }
                $this->sections[] = new ContentCourierSection($section,$this->pageURI,$section_config,$this->contentLoader,$content_processor);
            }            
        }

        /**
        * Detemines what sections we're dealing with
        *
        * Parses config file to detemine what the "valid" sections are
        * these string idenitfies are then used to detemine what section objects
        * to create in registerSection
        */         
        protected function registerSectionsFromConfig(){
            $aSectionConfigs = $this->siteConfig->getArrayOfSectionConfigs();
            foreach($aSectionConfigs as $sSectionName=>$oSectionConfig){
                $this->registerSection($sSectionName,$oSectionConfig);
            }
            
//             foreach($this->xmlConfig->sections->children() as $section){
//                 $this->registerSection($section->getName(),$section);
//             }
            if(0 == count($this->sections)){
                throw new Exception("No sections found");
            }
        }
        
        /**
        * Sets the pageURI for the request we're handling
        *
        * Perform any sanatizing here
        */                 
        protected function setPageURI(){
            $this->pageURI = '/';
            if(isset($_GET['uri'])){
                $this->pageURI .= $_GET['uri'];
            }
            //sanatize; only allow numbers, letters, "_" and "-"
            $this->pageURI = preg_replace('{[^a-zA-Z0-9/_-]}six','',$this->pageURI);
        }

        /**
        * Sets the HTML template for the page
        *
        * Parses config object to detemine if this Page URI has a custom
        * template, if not uses the default template
        */                 
        protected function setTemplate(){
            $mTemplateURI = $this->getCustomTemplateURI();
            if(!$mTemplateURI){
                $mTemplateURI = $this->getDefaultTemplateURI();
            }
            if('' == trim($mTemplateURI)){
                throw new Exception("Could not find template");
            }
            $this->loadTemplateURI($mTemplateURI);
        }
        
        /**
        * Loads the template string based on URI
        *
        * Overload here if you want to store your template somewhere other 
        * than teh root level of the site (up a few levels safe from prying eyes,
        * in a database, in a cache, etc.)
        * @param type $var_name
        * @return type $var_name
        */                 
        protected function loadTemplateURI($sTemplateURI){
            $this->template = file_get_contents($sTemplateURI);
            //$this->template = get_template();
        }
        
        /**
        * Grabs the custom template URI for this request, if any
        * @return mixed Either a string, or false if nothing's found
        */                 
        protected function getCustomTemplateURI(){        
            $sCustomURI = $this->siteConfig->getCustomTemplateURIForPage($this->pageURI);
            if(''==trim($sCustomURI)){
                return false;
            }
            else{
                return $sCustomURI;
            }            
        }

        /**
        * Grabs the default template to use for all pages
        * @return string
        */         
        protected function getDefaultTemplateURI(){
            $sTemplate = $this->siteConfig->getDefaultTemplateURI();
            if('' == trim($sTemplate)){
                throw new Exception("No templateURI attribute found in <site-config /> ");
            }
            return $sTemplate;
        }

        /**
        * Renders the page
        *
        * Pass in a boolean true to have the function reutrn the rendered string,
        * otherise will echo the page page for you
        * @param boolean $return
        * @return mixed nothing or string
        */                 
        public function render($return=false){            
            $sOutput = $this->template;
            foreach($this->sections as $section){
                $section_name   = $section->getName();
                $replace_token  = "<$section_name />";
                $content        = $section->render();
                //xslt/xquery here instead? smarty?                
                $sOutput = str_replace($replace_token,$content,$sOutput);
            }            
            
            if(!$return){
                echo $sOutput;
            }
            else{
                return $sOutput;
            }
        }
    }

    class ContentCourierConfig{}

    abstract class ContentCourierConfigSiteFactory extends ContentCourierConfig{
        static public function getInstance($type='XML',$uri_config){
            $type = trim($type);
            switch($type){
                default:
                    $type = ('' == trim($type)) ? 'XML' : $type;
                    $type = 'ContentCourierConfigSite'.$type;
                    $object = new $type($uri_config);                    
                    return $object;        
            }                
        }                
        
        abstract public function getStrict404Handling();
        abstract public function getDefaultContentProcessor();
        abstract public function getCustomTemplateURIForPage($page_uri);
        abstract public function getDefaultTemplateURI();
        abstract public function isValidPageURI($sPageURI);
        abstract public function getArrayOfSectionConfigs();
    }    

    class ContentCourierConfigSiteXML extends ContentCourierConfigSiteFactory{    
        protected $simpleXML;
        
        public function __construct($uri_config){
            $this->simpleXML = new SimpleXMlElement(trim(file_get_contents($uri_config)));
        }
        
        public function getStrict404Handling(){
            return (string) $this->simpleXML->{'valid-uri'}->attributes()->strict;            
        }
        
        public function getDefaultContentProcessor(){
            return (string) $this->simpleXML->sections->attributes()->defaultProcessor;
        }
        
        public function getCustomTemplateURIForPage($page_uri){
            $sXPath     = '/site-config/valid-uri/uri[string(.) = "'.$page_uri.'"]';
            $oURINode   = $this->simpleXML->xpath($sXPath);
            $sCustomURI = '';
            if(is_array($oURINode)){
                $oURINode   = $oURINode[0];
                $sCustomURI = (string) $oURINode->attributes()->templateURI;
            }            
            return $sCustomURI;
        }
        
        public function getDefaultTemplateURI(){
            return (string) $this->simpleXML->attributes()->templateURI;
        }        
        
        public function isValidPageURI($sPageURI){
            $sXPath = '//valid-uri/uri[string(.) = "'.$sPageURI.'"]';  
            $aMatches = $this->simpleXML->xpath($sXPath);
            if(count($aMatches) < 1){
                return false;
            }
            else{
                return true;
            }
        }        
        
        public function getArrayOfSectionConfigs(){
            $aArray = Array();
            foreach($this->simpleXML->sections->children() as $section){
                $sSectionName = $section->getName();
                $oSectionConfig = ContentCourierConfigSectionFactory::getInstance('XML',$section->asXML());
                $aArray[$sSectionName] = $oSectionConfig;
            }            
            return $aArray;
        }
    }    

    abstract class ContentCourierConfigSectionFactory extends ContentCourierConfig{
        static public function getInstance($type='XML',$xml){
            $type = trim($type);
            switch($type){
                default:
                    $type = ('' == trim($type)) ? 'XML' : $type;
                    $type = 'ContentCourierConfigSection'.$type;
                    $object = new $type($xml);                    
                    return $object;        
            }                
        }                    
        
        abstract public function getContentProcessor();
        abstract public function getPassthrough()       ;
        abstract public function getDefaultContentURI();
        abstract public function hasMap();
        abstract public function getContentURI($page_uri);
        abstract public function getCustomProcessor($page_uri);
        abstract public function hasMapMatch($page_uri);

    }
    
    class ContentCourierConfigSectionXML extends ContentCourierConfigSectionFactory{        
        protected $simpleXML;
        
        public function __construct($sXML){
            $this->simpleXML = new SimpleXMLElement($sXML);
        }
        
        public function getContentProcessor(){
            return (string) $this->simpleXML->attributes()->processor;
        }
        
        public function getPassthrough(){
            return (string) $this->simpleXML->attributes()->passthrough;
        }
                
        public function getDefaultContentURI(){
            return (string) $this->simpleXML->attributes()->defaultContentURI;
        }
        
        public function hasMap(){
            return $this->simpleXML->map;
        }
        
        public function getContentURI($page_uri){
            $sPath = '//map[@pageURI="'.$page_uri.'"]';
            $aResults = $this->simpleXML->xpath($sPath);
            if(is_array($aResults)){
                return $aResults[0]->attributes()->contentURI;
            }
        }
        
        public function getCustomProcessor($page_uri){
            $sCustomProcessor='';
            $sPath = '//map[@pageURI="'.$page_uri.'" and string(@processor)!=""]';
            $sxmlResults = $this->simpleXML->xpath($sPath);                                
            if(is_array($sxmlResults) && count($sxmlResults) > 0){
                $sxmlResults = $sxmlResults[0];
                $sCustomProcessor = (string) $sxmlResults->attributes()->processor;                    
                if('' == $sCustomProcessor){
                    throw new Exception("Empty Value for Custom Processor Found");
                }                    
            }   
            return $sCustomProcessor;
        }
        
        public function hasMapMatch($page_uri){
            foreach($this->simpleXML->map as $map){
                $sPageURI    = (string) $map->attributes()->pageURI;            
                if($sPageURI == $page_uri){
                    return true;
                }
            }
            return false;        
        }            
    }
    
    class ContentCourierSection{
        /**
        * The name of this section main, header, sidebar, adzone, etc.
        *
        * Importnat, used for string (or maybe xslt if I got unlazy) replacments in template 
        * @var string $name
        */             
        protected $name;
        
        /**
        * The page URI for this request
        * @var string $pageURI
        */                 
        protected $pageURI;
        
        /**
        * Oru loaded content for this sextion
        * @var string $content
        */                 
        protected $content;

        /**
        * Reference to our section config object
        * @var Object $sectionConfig
        */                 
        protected $sectionConfig;
        
        /**
        * Reference to teh object that lodas our content for us
        * @var Object $contentLoader
        */                 
        protected $contentLoader;

        /**
        * Reference to the obejct that will process the content for us
        *
        * Content processing is manipulations (ex. markdown) performed on the 
        * content post loading
        * @var Object $contentProcessor
        */                 
        protected $contentProcessor;

        /**
        * Standard Constructor
        *
        * Sets our name, assigns our propeties
        * @param type $section
        * @param type $page_uri
        * @param type $section_config
        * @param type $content_loader
        * @param type $content_processor
        */                 
        public function __construct($section,$page_uri,$section_config=false,$content_loader,$content_processor){
            $this->name = $section;
            $this->pageURI = $page_uri;
            $this->sectionConfig = $section_config;
            $this->contentLoader = $content_loader;
            
            $this->contentProcessor = ContentCourierProcessorFactory::getInstance($content_processor); 
        }        
        
        /**
        * Simple Getter
        * @todo Is this even used?
        * @return string
        */                 
        public function getName(){
            return $this->name;
        }

        /**
        * renders an individual section
        *
        * Loads the content, applied processing rules
        * @return string
        */                 
        public function render(){
            $this->loadContent();
            $this->setCustomProcessor();
            $this->applyProcessingRules();
            return $this->content;
        }

        /**
        * Loads the content for our section using the content loader
        *
        * Based on config
        * 1. Detemines the content URI for this section and page combo
        * 2. Passes URI to content loader
        *
        * "Does this section have any map nodes?": Use that
        * "If there aren't any map nodes, is there a passthrough? Then use
        *  the page URI as the content URI"
        * "Otherwise, use the default content URI" (useful for site wide ad blocks, sidebar,etc.)
        */                 
        protected function loadContent(){                        
            $sPassthrough = $this->sectionConfig->getPassthrough();
            $this->content = '';
            if('passthrough' == $sPassthrough && !$this->hasMapMatch()){
                if('/' != $this->pageURI){
                    $sContentURI = $this->contentLoader->applyPassthroughFilter($this->pageURI);                
                }
                else{                    
                    $sContentURI = $this->sectionConfig->getDefaultContentURI();
                }                
            }
            else if($this->sectionConfig->hasMap() && $this->hasMapMatch()){
                $sContentURI = (string) $this->sectionConfig->getContentURI($this->pageURI);
            }
            else{
                $sContentURI = $this->sectionConfig->getDefaultContentURI();
            }            
            $this->content .= $this->contentLoader->loadContent($sContentURI);
        }

        /**
        * Checks config for a custom processer and declares one
        *
        * Sections have  default processor, but there ay be times you want 
        * to apply some custom rules.  Use this (see ContentCourierProcessorFactory
        * for more information)
        */                 
        protected function setCustomProcessor(){
            if($this->hasMapMatch()){           
                $sCustomProcessor = $this->sectionConfig->getCustomProcessor($this->pageURI);                
                if($sCustomProcessor){
                    $this->contentProcessor = ContentCourierProcessorFactory::getInstance($sCustomProcessor);                                
                }
            }
        }

        /**
        * Is there a pageURI/contentURI map match for this section/request?
        * @return boolean
        */                 
        protected function hasMapMatch(){
            return $this->sectionConfig->hasMapMatch($this->pageURI);
        }
        
        /**
        * Calls the processContent method of the processing object
        */                 
        protected function applyProcessingRules(){
            //$this->content .= ', and then *some* processing rules would be applied';        
            $this->content = $this->contentProcessor->processContent($this->content);
            
        }        
    }
    abstract class ContentCourierLoaderFactory{
        public $contentCourierApplication;
        
        static public function getInstance($type){        
            $type = trim($type);
            switch($type){
                case 'filesystem':
                    return new ContentCourierLoaderFileSystem();
                default:
                    $type = 'ContentCourierLoader'.$type;
                    return new $type;
            }
        }
        
        abstract public function loadContent($uri);        
        abstract public function applyPassthroughFilter($uri);
    }        
        
    abstract class ContentCourierProcessorFactory{
        /**
        * Returns proper content processor
        *
        * You can set custom processors in the config file for sections, uris, etc.
        * This Factory is used to create the actual object.  There are a few default
        * processors (markdown, vowels currently).  If one of the defaults aren't found
        * we attempt to create an onkect with teh sepcified type, appended to 
        * ContentCourierProcessor.  If we're stuck with PHP we may as well take advantage of it
        * @param type $var_name
        * @return type $var_name
        */             
        static public function getInstance($type=''){
            $type = trim($type);        
            if(strpos($type, ',') !== false){
                $sProcessors = $type;
                $type = 'AssemblyLine';
            }
            switch($type){
                case 'AssemblyLine':
                    $object = new ContentCourierProcessorAssemblyLine($sProcessors);
                    return $object;
                    break;
                case 'markdown':
                    return new ContentCourierProcessorMarkdown();
                    break;
                default:
                    $type = ('' == trim($type) || 'none' == strToLower($type) ) ? 'None' : $type;
                    $type = 'ContentCourierProcessor'.$type;
                    $object = new $type;
                    return $object;
            }
        }
        
        public abstract function processContent($string);        
    }
    
    class ContentCourierProcessorAssemblyLine extends ContentCourierProcessorFactory{
        /**
        * comma seperated string of the processors we should use
        * @var string $processors
        */     
        protected $processors;
        
        public function __construct($string){
            $this->processors = $string;
        }
        
        public function processContent($string){
            $aProcessors = preg_split('{,}',$this->processors,NULL,PREG_SPLIT_NO_EMPTY);
            foreach($aProcessors as $sProcessor){                
                $oProcessor = ContentCourierProcessorFactory::getInstance($sProcessor);
                $string = $oProcessor->processContent($string);
            }
            return $string;                        
        }
    }    
    class ContentCourierProcessorNone extends ContentCourierProcessorFactory{    
        public function processContent($string){
            return $string;
        }
    }
?>
<?php
    class ContentCourierHTTP404_Exception extends Exception{
        /**
        * Called when we Want a 404 page
        * @param type $sMessage
        */             
        public function quitAndExit($sMessage){
            $page = <<<HTML
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>$sMessage</p>
</body></html>        
HTML;
            header("HTTP/1.1 404 Not Found"); 
            echo trim($page);
            exit;
        }
    }
?>